﻿/* Copyright 2011 Teun Duynstee
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MongoDB.Driver;
using MongoDB.Bson;
using System.Web.Security;
using System.Security.Cryptography;

namespace MongoAspnetProviders
{
    public class MembershipProvider : System.Web.Security.MembershipProvider
    {
        public override string ApplicationName{get;set;}
        public string ConnectionString { get; protected set; }
        public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
        {
            base.Initialize(name, config);
            foreach (string item in config.Keys)
            {
                if (item.ToLower() == "connectionstring")
                {
                    this.ConnectionString = config[item];
                }
            }

        }
        private IStoreWrapper<User> _collection = null;
        protected IStoreWrapper<User> UserCollection
        {
            get
            {
                if (_collection == null)
                {
                    MongoDatabase db = MongoDatabase.Create(this.ConnectionString);
                    MongoCollection<User> collection = db.GetCollection<User>("user");
                    DoCollectionInitialization(collection);
                    _collection = new DefaultStoreWrapper<User>(collection);
                }
                return _collection;
            }
        }

        private void DoCollectionInitialization(MongoCollection<User> collection)
        {
            collection.EnsureIndex("UserName");
            collection.EnsureIndex("Email");
        }


        public override bool ChangePassword(string username, string oldPassword, string newPassword)
        {
            User user = UserCollection.FindOne(FindByName(username));
            byte[] encrypted = Encrypt(oldPassword, user.Password.Salt);
            if (user.Password.HashedPassword.SequenceEqual(encrypted))
            {
                user.Password.HashedPassword = Encrypt(newPassword, user.Password.Salt);
                user.LastPasswordChangedDate = DateTime.Now.ToUniversalTime();
                user.LastActivityDate = DateTime.Now.ToUniversalTime();
                UpsertUser(user.UserName, user);
                return true;
            }
            else
            {
                return false;
            }
        }

        private byte[] Encrypt(string password, byte[] salt)
        {
            HashAlgorithm alg  = HashAlgorithm.Create();
            byte[] passwordBytes = Encoding.UTF8.GetBytes(password);
            byte[] clear = new byte[passwordBytes.Length + salt.Length];
            Buffer.BlockCopy(passwordBytes, 0, clear, 0, passwordBytes.Length);
            Buffer.BlockCopy(salt, 0, clear, passwordBytes.Length, salt.Length);
            byte[] result = alg.ComputeHash(clear);
            return result;
        }

        public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer)
        {
            throw new NotImplementedException();
        }
        Random rnd = new Random(DateTime.Now.Millisecond);
        public override System.Web.Security.MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out System.Web.Security.MembershipCreateStatus status)
        {
            byte[] newSalt = new byte[6];
            rnd.NextBytes(newSalt);
            User u = new User()
            {
                UserName = username,
                Email = email,
                Comment = "",
                IsApproved = isApproved,
                IsLockedOut = false,
                LastActivityDate = DateTime.Now.ToUniversalTime(),
                LastLockoutDate = DateTime.MinValue.ToUniversalTime(),
                LastLoginDate = DateTime.Now.ToUniversalTime(),
                LastPasswordChangedDate = DateTime.Now.ToUniversalTime(),
                CreationDate = DateTime.Now.ToUniversalTime(),
                Password = new User.PasswordStore()
                {
                    Salt= newSalt,
                    HashedPassword = Encrypt(password, newSalt)
                }
            };
            UpsertUser(username, u, out status);
            if (status == MembershipCreateStatus.Success)
            {
                return ToMembershipUser(u);
            }
            else
            {
                return null;
            }
        }

        private MembershipCreateStatus UpsertUser(string username, User u)
        {
            MembershipCreateStatus status;
            UpsertUser(username, u, out status);
            return status;
        }
        private void UpsertUser(string username, User u, out MembershipCreateStatus status)
        {
            UpdateDocument updatedoc = new UpdateDocument(
                    new BsonElement("UserName", u.UserName ?? ""),
                    new BsonElement("Email", u.Email ?? ""),
                    new BsonElement("Comment", u.Comment ?? ""),
                    new BsonElement("IsApproved", u.IsApproved),
                    new BsonElement("IsLockedOut", u.IsLockedOut),
                    new BsonElement("LastActivityDate", u.LastActivityDate),
                    new BsonElement("LastLockoutDate", u.LastLockoutDate),
                    new BsonElement("LastLoginDate", u.LastLoginDate),
                    new BsonElement("LastPasswordChangedDate", u.LastPasswordChangedDate),
                    new BsonElement("CreationDate", u.CreationDate)
                    );
            if (u.Password != null)
            {
                updatedoc.Add(
                    new BsonElement("Password", 
                        new BsonDocument(
                            new BsonElement("Salt", u.Password.Salt),
                            new BsonElement("HashedPassword", u.Password.HashedPassword)
                            )
                        )

                    );
            }
            UserCollection.Update(FindByName(username), updatedoc, UpdateFlags.Upsert);
            status = MembershipCreateStatus.Success;
        }

        public override bool DeleteUser(string username, bool deleteAllRelatedData)
        {
            SafeModeResult result = UserCollection.Remove(FindByName(username));
            return result.Ok;
        }

        public override bool EnablePasswordReset
        {
            get { return false; }
        }

        public override bool EnablePasswordRetrieval
        {
            get { return false; }
        }

        public override System.Web.Security.MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords)
        {
            return FindUsersByQuery(FindByEmail(emailToMatch), pageIndex, pageSize, out totalRecords);
        }

        public override System.Web.Security.MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)
        {
            return FindUsersByQuery(FindByName(usernameToMatch), pageIndex, pageSize, out totalRecords);
        }

        public override System.Web.Security.MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)
        {
            return FindUsersByQuery(new QueryDocument(), pageIndex, pageSize, out totalRecords);
        }

        private System.Web.Security.MembershipUserCollection FindUsersByQuery(IMongoQuery where, int pageIndex, int pageSize, out int totalRecords)
        {
            var users = UserCollection.Find(where, pageIndex * pageSize, pageSize, out totalRecords);
            MembershipUserCollection result = new MembershipUserCollection();
            foreach (var user in users)
            {
                result.Add(ToMembershipUser(user));
            }
            return result;
        }

        public override int GetNumberOfUsersOnline()
        {
            IMongoQuery whereRecentActivity = new QueryDocument(
                new BsonElement("LastActivityDate", 
                    new BsonDocument(new BsonElement("$gt", DateTime.Now.AddMinutes(-30).ToUniversalTime()))
                    )
                );
            int total;
            FindUsersByQuery(whereRecentActivity, 0, 0, out total);
            return total;

        }

        public override string GetPassword(string username, string answer)
        {
            throw new NotImplementedException();
        }

        public override System.Web.Security.MembershipUser GetUser(string username, bool userIsOnline)
        {
            User result = UserCollection.FindOne(FindByName(username));

            return ToMembershipUser(result);
        }

        public override System.Web.Security.MembershipUser GetUser(object providerUserKey, bool userIsOnline)
        {
            throw new NotImplementedException();
        }

        public override string GetUserNameByEmail(string email)
        {
            User u = UserCollection.FindOne(FindByEmail(email));
            if (u == null)
            {
                return null;
            }
            return u.UserName;
        }

        public override int MaxInvalidPasswordAttempts
        {
            get { throw new NotImplementedException(); }
        }

        public override int MinRequiredNonAlphanumericCharacters
        {
            get { throw new NotImplementedException(); }
        }

        public override int MinRequiredPasswordLength
        {
            get { throw new NotImplementedException(); }
        }

        public override int PasswordAttemptWindow
        {
            get { throw new NotImplementedException(); }
        }

        public override System.Web.Security.MembershipPasswordFormat PasswordFormat
        {
            get { throw new NotImplementedException(); }
        }

        public override string PasswordStrengthRegularExpression
        {
            get { throw new NotImplementedException(); }
        }

        public override bool RequiresQuestionAndAnswer
        {
            get { return false; }
        }

        public override bool RequiresUniqueEmail
        {
            get { return false; }
        }

        public override string ResetPassword(string username, string answer)
        {
            throw new NotImplementedException();
        }

        public override bool UnlockUser(string userName)
        {
            User user = UserCollection.FindOne(FindByName(userName));
            if (user == null || !user.IsLockedOut)
            {
                return false;
            }
            user.IsLockedOut = false;
            MembershipCreateStatus status;
            UpsertUser(userName, user, out status);
            return true;
        }

        public override void UpdateUser(System.Web.Security.MembershipUser user)
        {
            User u = FromMembershipUser(user);
            MembershipCreateStatus status;
            UpsertUser(u.UserName, u, out status);

        }

        public override bool ValidateUser(string username, string password)
        {
            User user = UserCollection.FindOne(FindByName(username));
            if (user == null || user.Password == null || user.Password.HashedPassword == null || user.Password.HashedPassword.Length < 1)
            {
                return false;
            }
            byte[] encrypted = Encrypt(password, user.Password.Salt);
            bool result = (user.Password.HashedPassword.SequenceEqual(encrypted));
            if (result)
            {
                user.LastLoginDate = DateTime.Now.ToUniversalTime();
                user.LastActivityDate = DateTime.Now.ToUniversalTime();
                UpsertUser(user.UserName, user);
            }
            return result;
        }


        private static IMongoQuery FindByName(string name)
        {
            return new QueryDocument(
                new BsonElement("UserName", new BsonString(name))
                );
        }
        private static IMongoQuery FindByEmail(string email)
        {
            return new QueryDocument(
                new BsonElement("Email", new BsonString(email))
                );
        }

        public MembershipUser ToMembershipUser(User u)
        {
            if (u == null) return null;
            MembershipUser result = new MembershipUser(this.Name, u.UserName, u.UserName, u.Email, "", u.Comment, u.IsApproved, u.IsLockedOut, u.CreationDate.ToLocalTime(), u.LastLoginDate.ToLocalTime(), u.LastActivityDate.ToLocalTime(), u.LastPasswordChangedDate.ToLocalTime(), u.LastLockoutDate.ToLocalTime());
            return result;
        }
        private User FromMembershipUser(MembershipUser u)
        {
            if (u == null) return null;
            User result = new User()
            {
                UserName = u.UserName,
                Email = u.Email,
                Comment = u.Comment,
                IsApproved = u.IsApproved,
                IsLockedOut = u.IsLockedOut,
                CreationDate = u.CreationDate.ToUniversalTime(),
                LastActivityDate = u.LastActivityDate.ToUniversalTime(),
                LastLockoutDate = u.LastLockoutDate.ToUniversalTime(),
                LastLoginDate = u.LastLoginDate.ToUniversalTime(),
                LastPasswordChangedDate = u.LastPasswordChangedDate.ToUniversalTime()
            };
            
            return result;
        }



    }
}
